package com.cloudkinto.service.order.allocate.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloudkinto.bo.order.allocate.*;
import com.cloudkinto.bo.order.inbound.OrderInputBoxAddBo;
import com.cloudkinto.bo.order.inbound.OrderInputBoxProductAddBo;
import com.cloudkinto.bo.order.inbound.OrderInputSaveBo;
import com.cloudkinto.bo.order.outbound.ExceptionConfirmBo;
import com.cloudkinto.bo.stock.ProductStockBySkuQueryBo;
import com.cloudkinto.bo.storage.CellAvailableBySkuQueryBo;
import com.cloudkinto.bo.storage.StorageLocationQueryBo;
import com.cloudkinto.common.BeanConvert;
import com.cloudkinto.common.RedisKeyConstants;
import com.cloudkinto.common.common.DateUtils;
import com.cloudkinto.common.constant.SysConstant;
import com.cloudkinto.common.exception.BizException;
import com.cloudkinto.common.utils.StringsUtil;
import com.cloudkinto.common.utils.dict.StaticDict;
import com.cloudkinto.common.web.PageResult;
import com.cloudkinto.common.web.SingleResult;
import com.cloudkinto.dao.AllocatePlanDao;
import com.cloudkinto.dto.stock.query.SkuStockPageDto;
import com.cloudkinto.entity.*;
import com.cloudkinto.enumType.address.AddressBookTypeEnum;
import com.cloudkinto.enumType.common.CountryEnum;
import com.cloudkinto.enumType.operate.OperateTypeEnum;
import com.cloudkinto.enumType.order.SendInEnum;
import com.cloudkinto.enumType.order.allocate.AllocatePackageType;
import com.cloudkinto.enumType.order.allocate.AllocatePlanStatus;
import com.cloudkinto.enumType.order.allocate.AllocatePlanStockLockStatus;
import com.cloudkinto.enumType.order.inbound.ArrivalModeEnum;
import com.cloudkinto.enumType.order.inbound.InboundTypeEnum;
import com.cloudkinto.enumType.order.inbound.OrderInputStatusEnum;
import com.cloudkinto.enumType.order.outbound.OutboundOrderKind;
import com.cloudkinto.enumType.stock.StockOrderTypeEnum;
import com.cloudkinto.enumType.stock.StockTypeEnum;
import com.cloudkinto.enumType.storage.StorageAreaEnum;
import com.cloudkinto.service.address.AddressBookService;
import com.cloudkinto.service.channel.LogisticsChannelService;
import com.cloudkinto.service.company.CompanyService;
import com.cloudkinto.service.event.order.AllocateAddCostEvent;
import com.cloudkinto.service.file.FileService;
import com.cloudkinto.service.operate.OperateRecordService;
import com.cloudkinto.service.order.allocate.AllocatePlanItemService;
import com.cloudkinto.service.order.allocate.AllocatePlanService;
import com.cloudkinto.service.order.input.OrderInputService;
import com.cloudkinto.service.order.outbound.OutboundCancelService;
import com.cloudkinto.service.order.outbound.OutboundService;
import com.cloudkinto.service.order.picking.OutboundPickingService;
import com.cloudkinto.service.platform.PlatformProductService;
import com.cloudkinto.service.platform.PlatformStoreService;
import com.cloudkinto.service.product.ProductService;
import com.cloudkinto.service.stock.ProductStockFlowingService;
import com.cloudkinto.service.stock.ProductStockRackService;
import com.cloudkinto.service.stock.ProductStockService;
import com.cloudkinto.service.storage.StorageLocationService;
import com.cloudkinto.service.storage.impl.StorageLocationServiceImpl;
import com.cloudkinto.service.user.UserService;
import com.cloudkinto.service.warehouse.WarehouseService;
import com.cloudkinto.vo.common.CommonBatchOperationVo;
import com.cloudkinto.vo.common.ListPageStatusStaDto;
import com.cloudkinto.vo.order.allocate.*;
import com.cloudkinto.vo.order.outbound.CellInfo;
import com.cloudkinto.vo.order.outbound.PackageSizeVo;
import com.cloudkinto.vo.stock.ProductFlowingOutEt;
import com.cloudkinto.vo.storage.CellAvailableVo;
import com.cloudkinto.vo.storage.StorageLocationVo;
import com.cloudkinto.vo.wr.cost.OrderOutCostReq;
import com.cloudkinto.vo.wr.trial.TrialBoxReq;
import com.cloudkinto.vo.wr.trial.TrialProductReq;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * <p>
 * 调拨发货计划 service实现类
 * </p>
 *
 * @author long
 * @since 2025-03-11
 */
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class AllocatePlanServiceImpl extends ServiceImpl<AllocatePlanDao, AllocatePlanDo> implements AllocatePlanService {

    private final PlatformProductService platformProductService;

    private final PlatformStoreService platformStoreService;

    private final LogisticsChannelService logisticsChannelService;

    private final AllocatePlanItemService allocatePlanItemService;

    private final RedisTemplate redisTemplate;

    private final WarehouseService warehouseService;

    private final FileService fileService;

    private final UserService userService;

    private final ProductStockService productStockService;

    private final AddressBookService addressBookService;

    private final OperateRecordService operateRecordService;

    private final ProductStockRackService productStockRackService;

    private final OutboundPickingService pickingService;

    private final OutboundService outboundService;

    private final CompanyService companyService;

    private final ProductService productService;

    private final StorageLocationService storageLocationService;

    private final OutboundCancelService cancelService;

    private final ProductStockFlowingService stockFlowingService;

    private final ApplicationContext context;
    private final OrderInputService orderInputService;

    /**
     * 用于stream流的filter方法做实体类的某个属性去重
     */
    static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        //putIfAbsent方法添加键值对，如果map集合中没有该key对应的值，则直接添加，并返回null，如果已经存在对应的值，则依旧为原来的值。
        //如果返回null表示添加数据成功(不重复)，不重复(null==null :TRUE)
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Override
    public Long add(AllocatePlanAddBo req, Long userId, String username) {
//        AllocatePlanDo planDo = new AllocatePlanDo();
//        BeanUtils.copyProperties(req, planDo);
        AllocatePlanDo planDo = BeanConvert.INSTANCE.allocateAddBoToAllocateDo(req);
        planDo.setPlanNo(generatePlanBatchNo());
        if (StrUtil.isNotBlank(req.getExpectedArrivalDate())) {
            planDo.setExpectedArrivalDate(DateUtil.parse(req.getExpectedArrivalDate(), "yyyy-MM-dd"));
        }
        if (StrUtil.isNotEmpty(req.getFileIds())) {
            fileService.uploadFileMult(req.getFileIds().split(","));
        }
        if (StrUtil.isNotBlank(req.getPlanShipmentTime())) {
            planDo.setPlanShipmentTime(DateUtil.parse(req.getPlanShipmentTime(), "yyyy-MM-dd"));
        }
        List<AllocatePlan3rdWhItemAddBo> planItem3rdWhList = req.getPlanItem3rdWhList();
        List<AllocatePlanFbaItemAddBo> planItemFbaList = req.getPlanItemFbaList();
        if (CollUtil.isEmpty(planItem3rdWhList) && CollUtil.isEmpty(planItemFbaList)) {
            throw new BizException(SysConstant.AllocatePlanNotProduct);
        }
        baseMapper.insert(planDo);
        if (CollUtil.isNotEmpty(planItem3rdWhList)) {
            List<AllocatePlanItemDo> planItemList = JSONUtil.toList(JSONUtil.toJsonStr(planItem3rdWhList), AllocatePlanItemDo.class);
            planItemList.forEach(planItem -> planItem.setOrderId(planDo.getId()));
            allocatePlanItemService.saveBatch(planItemList);
        }
        if (CollUtil.isNotEmpty(planItemFbaList)) {
            List<AllocatePlanItemDo> planItemList = new ArrayList<>();
            for (AllocatePlanFbaItemAddBo fbaItemAddBo : planItemFbaList) {
                AllocatePlanItemDo planItemDo = JSONUtil.toBean(JSONUtil.toJsonStr(fbaItemAddBo), AllocatePlanItemDo.class);
                planItemDo.setOrderId(planDo.getId());
                List<PairProductItemBo> productItems = fbaItemAddBo.getProductItems();
                if (CollUtil.isNotEmpty(productItems)) {
                    for (PairProductItemBo productItem : productItems) {
//                        AllocatePlanItemDo planItem = new AllocatePlanItemDo();
//                        BeanUtils.copyProperties(planItemDo, planItem);
                        AllocatePlanItemDo planItem = BeanConvert.INSTANCE.copyAllocateItemDo(planItemDo);
//                        BeanUtils.copyProperties(productItem, planItem);
                        BeanConvert.INSTANCE.pairProductItemBoToAllocateItemDo(productItem, planItem);
                        if (Objects.isNull(planItem.getPerBoxQty())) {
                            planItem.setPerBoxQty(0);
                        }
                        if (Objects.isNull(planItem.getBoxQty())) {
                            planItem.setBoxQty(0);
                        }
                        if (Objects.isNull(planItem.getPlanShipments())) {
                            planItem.setPlanShipments(0);
                        }
                        planItemList.add(planItem);
                    }
                } else {
                    planItemDo.setPerBoxQty(1);
                    planItemDo.setPlanShipments(planItemDo.getBoxQty());
                    if (Objects.isNull(planItemDo.getPerBoxQty())) {
                        planItemDo.setPerBoxQty(0);
                    }
                    if (Objects.isNull(planItemDo.getBoxQty())) {
                        planItemDo.setBoxQty(0);
                    }
                    if (Objects.isNull(planItemDo.getPlanShipments())) {
                        planItemDo.setPlanShipments(0);
                    }
                    planItemList.add(planItemDo);
                }
            }
            allocatePlanItemService.saveBatch(planItemList);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Allocate_Plan_Add,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        return planDo.getId();
    }

    @Override
    public void update(AllocatePlanUpdateBo bo, Long userId, String username) {
        AllocatePlanDo planDo = baseMapper.selectById(bo.getId());
//        BeanUtils.copyProperties(bo, planDo);
        BeanConvert.INSTANCE.allocateUpdateBoToAllocateDo(bo, planDo);
        if (!AllocatePlanStockLockStatus.zero.getValue().equals(planDo.getStockLockStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Exist_Lock_Stock);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        if (StrUtil.isNotBlank(bo.getPlanShipmentTime())) {
            planDo.setPlanShipmentTime(DateUtil.parse(bo.getPlanShipmentTime()));
        }
        if (StrUtil.isNotBlank(bo.getExpectedArrivalDate())) {
            planDo.setExpectedArrivalDate(DateUtil.parse(bo.getExpectedArrivalDate(), "yyyy-MM-dd"));
        }
        if (StrUtil.isNotEmpty(bo.getFileIds())) {
            fileService.uploadFileMult(bo.getFileIds().split(","));
        }
        baseMapper.updateById(planDo);
        List<AllocatePlan3rdWhItemUpdateBo> planItem3rdWhList = bo.getPlanItem3rdWhList();
        List<AllocatePlanFbaItemUpdateBo> planItemFbaList = bo.getPlanItemFbaList();
        if (CollUtil.isEmpty(planItem3rdWhList) && CollUtil.isEmpty(planItemFbaList)) {
            throw new BizException(SysConstant.AllocatePlanNotProduct);
        }
        allocatePlanItemService.remove(new LambdaQueryWrapper<AllocatePlanItemDo>().eq(AllocatePlanItemDo::getOrderId, planDo.getId()));
        if (CollUtil.isNotEmpty(planItem3rdWhList)) {
            List<AllocatePlanItemDo> planItemList = JSONUtil.toList(JSONUtil.toJsonStr(planItem3rdWhList), AllocatePlanItemDo.class);
            planItemList.forEach(planItem -> planItem.setOrderId(planDo.getId()));
            allocatePlanItemService.saveBatch(planItemList);
        }
        if (CollUtil.isNotEmpty(planItemFbaList)) {
            List<AllocatePlanItemDo> planItemList = new ArrayList<>();
            for (AllocatePlanFbaItemUpdateBo fbaItemAddBo : planItemFbaList) {
                AllocatePlanItemDo planItemDo = JSONUtil.toBean(JSONUtil.toJsonStr(fbaItemAddBo), AllocatePlanItemDo.class);
                planItemDo.setOrderId(planDo.getId());
                List<PairProductItemBo> productItems = fbaItemAddBo.getProductItems();
                if (CollUtil.isNotEmpty(productItems)) {
                    for (PairProductItemBo productItem : productItems) {
//                        AllocatePlanItemDo planItem = new AllocatePlanItemDo();
//                        BeanUtils.copyProperties(planItemDo, planItem);
                        AllocatePlanItemDo planItem = BeanConvert.INSTANCE.copyAllocateItemDo(planItemDo);
//                        BeanUtils.copyProperties(productItem, planItem);
                        BeanConvert.INSTANCE.pairProductItemBoToAllocateItemDo(productItem, planItem);
                        planItemList.add(planItem);
                    }
                } else {
                    planItemDo.setPerBoxQty(1);
                    planItemDo.setPlanShipments(planItemDo.getBoxQty());
                    planItemList.add(planItemDo);
                }
            }
            allocatePlanItemService.saveBatch(planItemList);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Allocate_Plan_Update,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
    }

    /**
     * 生成计划批次号
     */
    @Override
    public String generatePlanBatchNo() {
        // 每天重置计划批次号
        //生成计划批次号
        String planBatchNoSeq = String.valueOf(redisTemplate.opsForValue().increment(RedisKeyConstants.PLAN_BATCH_NO_SEQ));
        //序列号不足六位在前面补0
        if (planBatchNoSeq.length() < 4) {
            planBatchNoSeq = "000".substring(0, 4 - planBatchNoSeq.length()) + planBatchNoSeq;
        }
        return "PB"
               + DateUtil.format(DateUtil.date(), "yyMMdd")
               + planBatchNoSeq;
    }

    @Override
    public void delete(Long id, Long userId, String username) {
        AllocatePlanDo planDo = baseMapper.selectById(id);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        if (Objects.isNull(planDo.getStockLockStatus()) || !AllocatePlanStockLockStatus.zero.getValue().equals(planDo.getStockLockStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Exist_Lock_Stock);
        }
        baseMapper.deleteById(id);
        allocatePlanItemService.remove(Wrappers.<AllocatePlanItemDo>lambdaQuery().eq(AllocatePlanItemDo::getOrderId, id));
    }

    @Override
    public AllocatePlanBatchVo detail(AllocatePlanDetailBo bo, ProductStockBySkuQueryBo productStockBySkuQueryBo) {
        AllocatePlanDo planDo = baseMapper.selectById(bo.getBatchId());
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
//        AllocatePlanBatchVo batchVo = new AllocatePlanBatchVo();
//        BeanUtils.copyProperties(planDo, batchVo);
        AllocatePlanBatchVo batchVo = BeanConvert.INSTANCE.allocateDoToAllocateVo(planDo);
        batchVo.setBatchId(planDo.getId());
        Integer destinationType = bo.getDestinationType();
        if (Objects.nonNull(batchVo.getDeliveryWhId())) {
            //库存过滤仓库
            productStockBySkuQueryBo.setWhId(batchVo.getDeliveryWhId());
        }
        if (StrUtil.isNotBlank(batchVo.getFileIds())) {
            batchVo.setFileVoList(fileService.getFileVoByIds(batchVo.getFileIds()));
        }
        if (Objects.nonNull(batchVo.getAddressBookId())) {
            batchVo.setAddressBook(addressBookService.getById(batchVo.getAddressBookId()));
        }
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        productStockBySkuQueryBo.setProductIds(planItemDoList.stream().map(AllocatePlanItemDo::getProductId).collect(Collectors.toSet()));
        //查询库存信息
        PageResult<SkuStockPageDto> productListPageBySku = productStockService.productListPageBySku(productStockBySkuQueryBo);
        Map<Long, SkuStockPageDto> productIdMatchStock = new HashMap<>();
        if (Objects.nonNull(productListPageBySku) && CollUtil.isNotEmpty(productListPageBySku.getData())) {
            productIdMatchStock = productListPageBySku.getData().stream().collect(Collectors.toMap(SkuStockPageDto::getProductId, val -> val, (i1, i2) -> i1));
        }
        if (destinationType.equals(1)) {
            Map<Long, List<AllocatePlanItemDo>> itemByPlatProductIdGroup = planItemDoList.stream().collect(Collectors.groupingBy(AllocatePlanItemDo::getPlatformProductId));
            List<AllocatePlanFbaItemVo> planItemVoList = new ArrayList<>();
            for (Map.Entry<Long, List<AllocatePlanItemDo>> entry : itemByPlatProductIdGroup.entrySet()) {
                List<AllocatePlanItemDo> itemDoList = entry.getValue();
                AllocatePlanItemDo planItemDo = itemDoList.get(0);
                AllocatePlanFbaItemVo fbaItemVo = JSONUtil.toBean(JSONUtil.toJsonStr(planItemDo), AllocatePlanFbaItemVo.class);
                fbaItemVo.setPlanId(planItemDo.getId());
                List<AllocatePlanFbaItemVo.PairProductItem> pairProductItems = JSONUtil.toList(JSONUtil.toJsonStr(itemDoList), AllocatePlanFbaItemVo.PairProductItem.class);
                for (AllocatePlanFbaItemVo.PairProductItem pairProductItem : pairProductItems) {
                    if (Objects.nonNull(pairProductItem.getProductId())) {
                        SkuStockPageDto skuStockPageDto = productIdMatchStock.get(pairProductItem.getProductId());
                        if (Objects.nonNull(skuStockPageDto)) {
                            pairProductItem.setDeliveryWhAvailableStock(skuStockPageDto.getAvailableAmount());
                            pairProductItem.setDeliveryWhInTransitStock(skuStockPageDto.getTransportAmount());
                        }
                    }
                }
                fbaItemVo.setPairProductItemList(pairProductItems);
                planItemVoList.add(fbaItemVo);
            }
            batchVo.setPlanItemFbaVoList(planItemVoList);
        } else if (destinationType.equals(2)) {
            List<AllocatePlan3rdWhItemVo> planItemVoList = JSONUtil.toList(JSONUtil.toJsonStr(planItemDoList), AllocatePlan3rdWhItemVo.class);
            for (AllocatePlan3rdWhItemVo planItemVo : planItemVoList) {
                planItemVo.setPlanId(planDo.getId());
                if (Objects.nonNull(planItemVo.getProductId())) {
                    SkuStockPageDto skuStockPageDto = productIdMatchStock.get(planItemVo.getProductId());
                    if (Objects.nonNull(skuStockPageDto)) {
                        planItemVo.setDeliveryWhAvailableStock(skuStockPageDto.getAvailableAmount());
                        planItemVo.setDeliveryWhInTransitStock(skuStockPageDto.getTransportAmount());
                    }
                }
            }
            batchVo.setPlanItem3rdWhVoList(planItemVoList);
        }
        batchVo.setOperateResList(operateRecordService.getOperateList(batchVo.getBatchId(), OperateTypeEnum.AllocatePlan));
        return batchVo;
    }

    @Override
    public SingleResult<Map<String, Object>> pageInit(Long userId, Long tenantId, Long companyId, String source) {
        Map<String, Object> map = new HashMap<>(16);
        map.put("allocatePackageType", AllocatePackageType.getAllocatePackageTypeList());
        map.put("allocateStatusList", AllocatePlanStatus.getAllocatePlanStatusList());
        map.put("allocatePlanStockLockStatus", AllocatePlanStockLockStatus.getAllocatePlanStockLockStatusList());
        map.put("platformStoreList", platformStoreService.lambdaQuery().eq(PlatformStoreDo::getPlatformCode, "amazon").eq(Objects.nonNull(companyId), PlatformStoreDo::getCompanyId, companyId).eq(Objects.nonNull(tenantId), PlatformStoreDo::getTenantId, tenantId).list());
        map.put("logisticsChannelList", logisticsChannelService.lambdaQuery().eq(LogisticsChannelDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE).list());
        if ("oms".equals(source)) {
            map.put("warehouseList", warehouseService.getWarehouseCompany(companyId, tenantId));
        } else {
            map.put("warehouseList", warehouseService.getList(tenantId));
        }
        map.put(
                "userList",
                userService.lambdaQuery()
                        .eq(UserDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                        .eq(UserDo::getUserType, 2)
                        .eq(UserDo::getCompanyId, companyId)
                        .list()
        );
        map.put("addressBookTypeList", AddressBookTypeEnum.getAddressBookTypeList());
        map.put("countryList", CountryEnum.getCountryEnumList());
        map.put("sendOutType", SendInEnum.getSendOutTypeList());
        map.put("companyTagList", addressBookService.listObjs(Wrappers.<AddressBookDo>lambdaQuery().select(AddressBookDo::getCompanyTag).eq(Objects.nonNull(tenantId), AddressBookDo::getTenantId, tenantId).eq(Objects.nonNull(companyId), AddressBookDo::getCompanyId, companyId).isNotNull(AddressBookDo::getCompanyTag), ObjectUtil::toString).stream().filter(StrUtil::isNotBlank).collect(Collectors.toSet()));
        return SingleResult.success(map);
    }

    @Override
    public PageResult<AllocatePlanBatchVo> getListPage(AllocatePlanQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<AllocatePlanBatchVo> list = baseMapper.getListPage(bo);
        PageInfo<AllocatePlanBatchVo> page = new PageInfo<>(list);
        return new PageResult<>(this.handlerListAfter(page.getList()), current, pageSize, (int) page.getTotal(), page.getPages());
    }

    private List<AllocatePlanBatchVo> handlerListAfter(List<AllocatePlanBatchVo> list) {
        if (CollUtil.isNotEmpty(list)) {
            list.forEach(planBatchVo -> {

            });
        }
        return list;
    }

    @Override
    public PageResult<AllocateProductListVo> platformProductListPage(AllocatePlanProductQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<AllocateProductListVo> list = platformProductService.getAllocatePlanProductListPage(bo);
        PageInfo<AllocateProductListVo> page = new PageInfo<>(list);
        return new PageResult<>(list, current, pageSize, (int) page.getTotal(), page.getPages());
    }

    @Override
    public void markingUrgency(AllocateUrgencyOperateBo bo, Long userId, String username) {
        this.lambdaUpdate()
                .in(AllocatePlanDo::getId, bo.getBatchIdList())
                .set(AllocatePlanDo::getUrgent, Boolean.TRUE)
                .update();
        for (Long batchId : bo.getBatchIdList()) {
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Allocate_Marking_Urgency,
                    "",
                    batchId,
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
        }
    }

    @Override
    public void cancelUrgency(AllocateUrgencyOperateBo bo, Long userId, String username) {
        this.lambdaUpdate()
                .in(AllocatePlanDo::getId, bo.getBatchIdList())
                .set(AllocatePlanDo::getUrgent, Boolean.FALSE)
                .update();
        for (Long batchId : bo.getBatchIdList()) {
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Allocate_Cancel_Urgency,
                    "",
                    batchId,
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
        }
    }

    @Override
    public void removePlanItem(AllocatePlanRemoveItemBo bo, Long userId, String username) {
        Long batchId = bo.getBatchId();
        AllocatePlanDo planDo = baseMapper.selectById(batchId);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        List<Long> planIdList = bo.getPlanIdList();
        allocatePlanItemService.remove(
                Wrappers.<AllocatePlanItemDo>lambdaQuery()
                        .eq(AllocatePlanItemDo::getOrderId, batchId)
                        .in(AllocatePlanItemDo::getId, planIdList)
        );
    }

    @Override
    public SingleResult<List<Map<String, Object>>> getListPageStatusSta(AllocatePlanQueryBo bo) {
        bo.setStatus(null);
        Map<Integer, Integer> data = baseMapper.getListPageStatusSta(bo).stream().collect(Collectors.toMap(ListPageStatusStaDto::getStatus, ListPageStatusStaDto::getQty));
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (AllocatePlanStatus statusEnum : AllocatePlanStatus.values()) {
            Map<String, Object> map = new HashMap<>(8);
            map.put("id", statusEnum.getValue());
            map.put("name", statusEnum.getDesc());
            map.put("sum", data.getOrDefault(statusEnum.getValue(), 0));
            resultList.add(map);
        }
        return SingleResult.success(resultList);
    }

    @Override
    public AllocatePlanLockStockVo lockStock(Long batchId, Long tenantId, Long userId, String username) {
        AllocatePlanDo planDo = baseMapper.selectById(batchId);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        if (!AllocatePlanStockLockStatus.zero.getValue().equals(planDo.getStockLockStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Exist_Lock_Stock);
        }
        AllocatePlanLockStockVo vo = new AllocatePlanLockStockVo();
        //查库存后锁定，按最大可用锁定，返回锁定数量
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, batchId)
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        if (planItemDoList.stream().allMatch(item -> item.getProductId() == null)) {
            throw new BizException(SysConstant.Allocate_Plan_Exist_Unpaired_Product);
        }
        for (AllocatePlanItemDo planItemDo : planItemDoList) {
            List<ProductStockRackDo> productStockRackDoList = productStockRackService.getFirstInFirstOutBySku(
                    tenantId,
                    planDo.getDeliveryWhId(),
                    planItemDo.getProductId(),
                    planItemDo.getPlanShipments(),
                    null
            );
            //需要锁定的数量
            AtomicInteger needLock = new AtomicInteger(planItemDo.getPlanShipments());
            //锁定库存并保存拣货记录返回差异数量
            int unLockNum = this.lockStockAndSavePicking(
                    productStockRackDoList,
                    needLock,
                    planItemDo.getId(),
                    Boolean.FALSE
            );
            planItemDo.setLockQty(planItemDo.getPlanShipments() - unLockNum);
        }
        Integer lockQty = planItemDoList.stream().map(AllocatePlanItemDo::getLockQty).reduce(0, Integer::sum);
        Integer planQty = planItemDoList.stream().map(AllocatePlanItemDo::getPlanShipments).reduce(0, Integer::sum);
        if (lockQty.equals(0)) {
            vo.setStockLockStatus(AllocatePlanStockLockStatus.zero.getValue());
        } else if (lockQty.equals(planQty)) {
            vo.setStockLockStatus(AllocatePlanStockLockStatus.one.getValue());
        } else {
            vo.setStockLockStatus(AllocatePlanStockLockStatus.two.getValue());
        }
        allocatePlanItemService.updateBatchById(planItemDoList);
        if (!AllocatePlanStockLockStatus.zero.getValue().equals(vo.getStockLockStatus())) {
            this.lambdaUpdate()
                    .eq(AllocatePlanDo::getId, batchId)
                    .set(AllocatePlanDo::getStockLockStatus, vo.getStockLockStatus())
                    .update();
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Allocate_Plan_Lock_Stock,
                    "",
                    planDo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
        }
        vo.setId(batchId);
        vo.setPlanNo(planDo.getPlanNo());
        vo.setDestinationType(planDo.getDestinationType());
        vo.setPlanItemDoList(planItemDoList);
        //锁定全部自动提交，锁定部分人工确认，
        if (AllocatePlanStockLockStatus.one.getValue().equals(vo.getStockLockStatus())) {
            //提交订单到wms
            this.submitOrderToWms(batchId, userId, username);
        }
        return vo;
    }

    /**
     * 锁库存并保存拣货记录
     *
     * @param productStockRackDoList 可以锁定的库存数据
     * @param needLock               需要锁定的数量
     * @param allocatePlanItemId     调拨产品表id
     * @return 差异数（未锁定数）
     */
    public int lockStockAndSavePicking(
            List<ProductStockRackDo> productStockRackDoList,
            AtomicInteger needLock,
            Long allocatePlanItemId,
            Boolean pickFlag) throws BizException {
        if (CollUtil.isEmpty(productStockRackDoList)) {
            return needLock.get();
        }
        List<OutboundPickingDo> outboundPickingDoList = new ArrayList<>();
        for (ProductStockRackDo productStockRackDo : productStockRackDoList.stream().sorted(Comparator.comparing(ProductStockRackDo::getId)).collect(Collectors.toList())) {
            if (needLock.get() <= 0) {
                break;
            }
            //该条记录可用数量
            Integer quantity = productStockRackDo.getQuantity();
            if (quantity == 0) {
                continue;
            }
            int qty;
            if (quantity < needLock.get()) {
                qty = quantity;
                needLock.set(needLock.get() - quantity);
            } else {
                qty = needLock.get();
                needLock.set(0);
            }
            //锁定库存
            productStockRackService.lambdaUpdate()
                    .eq(ProductStockRackDo::getId, productStockRackDo.getId())
                    .set(ProductStockRackDo::getQuantity, quantity - qty)
                    .set(ProductStockRackDo::getLockQuantity, productStockRackDo.getLockQuantity() + qty)
                    .update();
            OutboundPickingDo outboundPickingDo = new OutboundPickingDo();
            outboundPickingDo.setOrderType(OutboundOrderKind.allocate.getValue());
            outboundPickingDo.setOutboundProductId(allocatePlanItemId);
            outboundPickingDo.setSubStockId(productStockRackDo.getId());
            outboundPickingDo.setRackId(productStockRackDo.getRackId());
            outboundPickingDo.setQty(qty);
            outboundPickingDo.setPickFlag(pickFlag);
            outboundPickingDoList.add(outboundPickingDo);
        }
        //批量保存系统默认拣货记录
        pickingService.saveBatch(outboundPickingDoList);
        return needLock.get();
    }

    @Override
    public void submitOrderToWms(Long batchId, Long userId, String username) {
        AllocatePlanDo planDo = baseMapper.selectById(batchId);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, batchId)
                .set(AllocatePlanDo::getStatus, AllocatePlanStatus.one.getValue())
                .set(AllocatePlanDo::getPushWmsTime, DateUtil.date())
                .update();
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Allocate_Plan_Submit_To_Wms,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        // 三方仓同步创建wms入库单数据
        Integer destinationType = planDo.getDestinationType();
        if (destinationType.equals(2)) {
            Long destinationWhId = planDo.getDestinationWhId();
            if (Objects.nonNull(destinationWhId)) {
                orderInputService.save(this.buildOrderInputSaveBo(planDo), userId, username, planDo.getTenantId(), planDo.getCompanyId());
            }
        }
    }

    private OrderInputSaveBo buildOrderInputSaveBo(AllocatePlanDo planDo) {
        //调拨产品信息
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .gt(AllocatePlanItemDo::getLockQty, 0)
                .list();
        Map<Long, ProductDo> productIdMatchDo = productService.lambdaQuery()
                .in(ProductDo::getId, planItemDoList.stream().map(AllocatePlanItemDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        OrderInputSaveBo bo = new OrderInputSaveBo();
        bo.setPostJdyInputOrder(true);
        bo.setWarehouseId(planDo.getDestinationWhId());
        bo.setArriveMethod(ArrivalModeEnum.one.getValue());
        bo.setTrackingNumber(planDo.getTrackingNumber());
        bo.setReferenceNumber(planDo.getPlanNo());
        bo.setInboundType(InboundTypeEnum.one.getValue());
        bo.setSkuTotalQty(planItemDoList.stream().map(AllocatePlanItemDo::getLockQty).reduce(0, Integer::sum));
        bo.setStatus(OrderInputStatusEnum.one.getValue());
        Date expectedArrivalDate = planDo.getExpectedArrivalDate();
        if (Objects.nonNull(expectedArrivalDate)) {
            bo.setExpectedDate(DateUtil.format(expectedArrivalDate, "yyyy-MM-dd"));
        }
        bo.setRemark(planDo.getRemark());
        List<OrderInputBoxAddBo> boxList = new ArrayList<>();
        AtomicInteger boxSeq = new AtomicInteger(1);
        for (AllocatePlanItemDo planItemDo : planItemDoList) {
            ProductDo productDo = productIdMatchDo.get(planItemDo.getProductId());
            if (productDo == null) {
                continue;
            }
            TrialBoxReq boxReq = this.calculatePackageSize(
                    Collections.singletonList(
                            PackageSizeVo.builder()
                                    .length(productDo.getWmsLength() == null ? productDo.getLength() : productDo.getWmsLength())
                                    .width(productDo.getWmsWidth() == null ? productDo.getWidth() : productDo.getWmsWidth())
                                    .height(productDo.getWmsHeight() == null ? productDo.getHeight() : productDo.getWmsHeight())
                                    .weight(productDo.getWmsWeight() == null ? productDo.getWeight() : productDo.getWmsWeight())
                                    .qty(planItemDo.getLockQty())
                                    .build()
                    )
            );
            OrderInputBoxAddBo boxAddBo = new OrderInputBoxAddBo();
            OrderInputBoxProductAddBo productAddBo = new OrderInputBoxProductAddBo();
            productAddBo.setProductId(planItemDo.getProductId());
            productAddBo.setSku(planItemDo.getSku());
            productAddBo.setPerBoxQty(planItemDo.getLockQty());
            boxAddBo.setSkuTotalQty(planItemDo.getLockQty());
            boxAddBo.setSkuKind(1);
            boxAddBo.setProductList(Collections.singletonList(productAddBo));
            boxAddBo.setBoxMarkNumberSeq1(boxSeq.getAndIncrement());
            boxAddBo.setBoxMarkNumberSeq2(1);
            boxAddBo.setLength(boxReq.getLength());
            boxAddBo.setLengthBs(boxReq.getLengthBs());
            boxAddBo.setWidth(boxReq.getWidth());
            boxAddBo.setWidthBs(boxReq.getWidthBs());
            boxAddBo.setHeight(boxReq.getHeight());
            boxAddBo.setHeightBs(boxReq.getHeightBs());
            boxAddBo.setWeight(boxReq.getWeight());
            boxAddBo.setWeightBs(boxReq.getWeightBs());
            boxList.add(boxAddBo);
        }
        bo.setBoxList(boxList);
        return bo;
    }

    @Override
    public void unLockStock(Long batchId, Long tenantId, Long userId, String username) {
        AllocatePlanDo planDo = baseMapper.selectById(batchId);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.zero.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_Status_Cannot_Operate);
        }
        if (AllocatePlanStockLockStatus.zero.getValue().equals(planDo.getStockLockStatus())) {
            throw new BizException(SysConstant.Allocate_Plan_No_Exist_Lock_Stock);
        }
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, batchId)
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        if (CollUtil.isNotEmpty(planItemDoList)) {
            for (AllocatePlanItemDo planItemDo : planItemDoList) {
                Integer lockQty = planItemDo.getLockQty();
                if (lockQty > 0) {
                    //拿到待拣的锁定库存记录
                    List<OutboundPickingDo> noPickingDoList = pickingService.lambdaQuery()
                            .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                            .eq(OutboundPickingDo::getOutboundProductId, planItemDo.getId())
                            .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                            .list();
                    outboundService.unLock(JSONUtil.toList(JSONUtil.toJsonStr(noPickingDoList), OutboundPickingDo.class), noPickingDoList, new AtomicInteger(lockQty));
                    allocatePlanItemService.lambdaUpdate()
                            .eq(AllocatePlanItemDo::getId, planItemDo.getId())
                            .set(AllocatePlanItemDo::getLockQty, 0)
                            .update();
                }
            }
        }
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, batchId)
                .set(AllocatePlanDo::getStockLockStatus, AllocatePlanStockLockStatus.zero.getValue())
                .update();
        operateRecordService.operateAddRecordAndValue(
                SysConstant.Allocate_Plan_UnLock_Stock,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
    }

    @Override
    public SingleResult allocateSubmitPick(List<Long> ids, Long userId, String username) {
        if (CollUtil.isEmpty(ids)) {
            throw new BizException(SysConstant.Please_Select_Allocate_Pick);
        }
        for (Long id : ids) {
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Allocate_Submit_Pick,
                    "",
                    id,
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
            //提交时间
            this.lambdaUpdate()
                    .eq(AllocatePlanDo::getId, id)
                    .set(AllocatePlanDo::getPickingPushTime, DateUtil.date())
                    .set(AllocatePlanDo::getStatus, AllocatePlanStatus.two.getValue())
                    .update();
        }
        return SingleResult.success();
    }

    @Override
    public Map<String, Object> allocatePickingDetailInit(Long userId, Long tenantId, String source) {
        Map<String, Object> map = new HashMap<>(8);
        map.put("companyList", companyService.getCompanyList(userId, tenantId, source));
        map.put("storageAreaList", StorageAreaEnum.getMapList());
        map.put("storageLocationType", StorageLocationServiceImpl.storageLocationTypeMap);
        return map;
    }

    @Override
    public AllocatePickingDetail allocatePickingDetail(Long id) {
        AllocatePlanDo planDo = this.getById(id);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.two.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.PickDetail_Status_Error);
        }
//        AllocatePickingDetail detail = new AllocatePickingDetail();
//        BeanUtils.copyProperties(planDo, detail);
        AllocatePickingDetail detail = BeanConvert.INSTANCE.allocateDoToAllocatePickingDetail(planDo);
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, id)
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .apply("actual_shipments < lock_qty")
                .list();
        if (CollUtil.isNotEmpty(planItemDoList)) {
            //根据出库产品查询推荐的拣货信息
            Map<Long, List<OutboundPickingDo>> packMatchPicking = pickingService.lambdaQuery()
                    .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                    .in(
                            OutboundPickingDo::getOutboundProductId,
                            planItemDoList.stream()
                                    .map(AllocatePlanItemDo::getId)
                                    .collect(Collectors.toList())
                    )
                    .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                    .list().stream()
                    .collect(Collectors.groupingBy(OutboundPickingDo::getOutboundProductId));
            planItemDoList = planItemDoList.stream()
                    .filter(item -> packMatchPicking.containsKey(item.getId()))
                    .collect(Collectors.toList());
            if (CollUtil.isNotEmpty(planItemDoList)) {
                Map<Long, ProductDo> productIdMatchDo = productService.lambdaQuery()
                        .in(
                                ProductDo::getId,
                                planItemDoList.stream()
                                        .map(AllocatePlanItemDo::getProductId)
                                        .collect(Collectors.toList())
                        )
                        .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
                for (AllocatePlanItemDo planItemDo : planItemDoList) {
                    AllocatePickingDetail.AllocatePicking allocatePicking = new AllocatePickingDetail.AllocatePicking();
                    List<OutboundPickingDo> pickingDoList = packMatchPicking.get(planItemDo.getId());
                    if (CollUtil.isEmpty(pickingDoList)) {
                        continue;
                    }
                    ProductDo productDo = productIdMatchDo.get(planItemDo.getProductId());
                    if (productDo == null) {
                        continue;
                    }
                    //合并相同库位
                    pickingDoList = new ArrayList<>(pickingDoList.stream().collect(Collectors.toMap(OutboundPickingDo::getRackId, val -> val, (o1, o2) -> {
                        o1.setQty(o1.getQty() + o2.getQty());
                        return o1;
                    })).values());
                    Map<Long, CellAvailableVo> cellAvailableDtoMap = storageLocationService.getCellAvailableByProductId(
                            CellAvailableBySkuQueryBo.builder()
                                    .tenantId(planDo.getTenantId())
                                    .allocatePlanItemId(planItemDo.getId())
                                    .productId(planItemDo.getProductId())
                                    .cellIdList(
                                            pickingDoList.stream()
                                                    .map(OutboundPickingDo::getRackId)
                                                    .collect(Collectors.toList())
                                    )
                                    .build()
                    ).stream().collect(Collectors.toMap(CellAvailableVo::getCellId, val -> val));
                    for (OutboundPickingDo pickingDo : pickingDoList) {
                        allocatePicking.getCellInfoList().add(
                                CellInfo.builder()
                                        .cellId(pickingDo.getRackId())
                                        .cellCode(cellAvailableDtoMap.get(pickingDo.getRackId()).getCellCode())
                                        .availableQty(cellAvailableDtoMap.get(pickingDo.getRackId()).getAvailableQty())
                                        .qty(pickingDo.getQty())
                                        .build()
                        );
                    }
                    allocatePicking.setAllocatePlanItemId(planItemDo.getId());
                    allocatePicking.setProductId(planItemDo.getProductId());
                    allocatePicking.setSku(productDo.getSku());
                    allocatePicking.setFnsku(productDo.getFnSku());
                    allocatePicking.setMainBarcode(productDo.getJanCode());
                    allocatePicking.setOutboundQty(planItemDo.getActualShipments());
                    allocatePicking.setForecastQty(planItemDo.getLockQty());
                    detail.getPickingList().add(allocatePicking);
                }
            }
        }
        return detail;
    }

    @Override
    public boolean allocatePicking(AllocatePickingBo bo, Long userId, String username, Long tenantId) {
        AllocatePlanDo planDo = this.getById(bo.getId());
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (!AllocatePlanStatus.two.getValue().equals(planDo.getStatus())) {
            throw new BizException(SysConstant.PickDetail_Status_Error);
        }
        List<AllocatePickingBo.AllocatePickingDetail> pickingDetails = bo.getAllocatePickingDetailList();
        if (CollUtil.isEmpty(pickingDetails)) {
            throw new BizException(SysConstant.PickDetail_Empty_Error);
        }
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, bo.getId())
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        Map<Long, List<OutboundPickingDo>> planItemIdMatchNoPickingList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .eq(OutboundPickingDo::getPickFlag, Boolean.FALSE)
                .in(OutboundPickingDo::getOutboundProductId, pickingDetails.stream().map(AllocatePickingBo.AllocatePickingDetail::getAllocatePlanItemId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.groupingBy(OutboundPickingDo::getOutboundProductId));
        //出库方式
        for (AllocatePickingBo.AllocatePickingDetail pickingDetail : pickingDetails) {
            //查询未拣货的记录（推荐库位）
            List<OutboundPickingDo> noPickingDoList = planItemIdMatchNoPickingList.get(pickingDetail.getAllocatePlanItemId());
            if (CollUtil.isEmpty(noPickingDoList)) {
                continue;
            }
            Map<Long, List<OutboundPickingDo>> noPickingDoListMap = noPickingDoList.stream()
                    .collect(Collectors.groupingBy(OutboundPickingDo::getRackId));
            //实际库位
            List<AllocatePickingBo.AllocatePickingCell> pickingCellList = pickingDetail.getPickingCellList();
            Map<Long, AllocatePickingBo.AllocatePickingCell> pickingCellMap = pickingCellList.stream()
                    .collect(Collectors.toMap(AllocatePickingBo.AllocatePickingCell::getCellId, val -> val, (o1, o2) -> {
                        o1.setQty(o1.getQty() + o2.getQty());
                        return o1;
                    }));
            //计算推荐/实际拣货总数
            Integer noPickingTotalQty = noPickingDoList.stream().map(OutboundPickingDo::getQty).reduce(0, Integer::sum);
            Integer pickingTotalQty = pickingCellMap.values().stream().map(AllocatePickingBo.AllocatePickingCell::getQty).reduce(0, Integer::sum);
            //比较推荐/实际拣货总数
            if (pickingTotalQty <= noPickingTotalQty) {
                //实际拣货库位在推荐库位中
                for (Map.Entry<Long, List<OutboundPickingDo>> entry : noPickingDoListMap.entrySet()) {
                    Long rackId = entry.getKey();
                    List<OutboundPickingDo> pickingDoList = entry.getValue();
                    //计算推荐库位拣货数量
                    Integer noPickingQty = pickingDoList.stream().map(OutboundPickingDo::getQty).reduce(0, Integer::sum);
                    AllocatePickingBo.AllocatePickingCell pickingCell = pickingCellMap.get(rackId);
                    if (Objects.isNull(pickingCell)) {
                        continue;
                    }
                    //实际库位拣货数量
                    Integer pickingQty = pickingCell.getQty();
                    if (pickingQty < noPickingQty) {
                        //实际拣货数量小于推荐拣货数量 eg.推荐拣货数量为3,实际拣货数量为1的情况
                        AtomicInteger needUpdate = new AtomicInteger(pickingQty);
                        //修改推荐库位的状态
                        pickingDoList.stream()
                                .sorted(Comparator.comparing(OutboundPickingDo::getId))
                                .forEach(item -> {
                                    if (needUpdate.get() == 0) {
                                        //跳过档次循环
                                        return;
                                    }
                                    if (item.getQty() <= needUpdate.get()) {
                                        pickingService.lambdaUpdate()
                                                .eq(OutboundPickingDo::getId, item.getId())
                                                .set(OutboundPickingDo::getPickFlag, Boolean.TRUE)
                                                .update();
                                        noPickingDoList.forEach(noPickingDo -> {
                                            if (noPickingDo.getId().equals(item.getId())) {
                                                noPickingDo.setPickFlag(Boolean.TRUE);
                                            }
                                        });
                                        needUpdate.set(needUpdate.get() - item.getQty());
                                    } else {
                                        pickingService.lambdaUpdate()
                                                .eq(OutboundPickingDo::getId, item.getId())
                                                .setSql("qty = qty -" + needUpdate.get())
                                                .update();
                                        //修改待拣货列表数据
                                        noPickingDoList.forEach(noPickingDo -> {
                                            if (noPickingDo.getId().equals(item.getId())) {
                                                noPickingDo.setQty(noPickingDo.getQty() - needUpdate.get());
                                            }
                                        });
//                                        OutboundPickingDo pickingDo = new OutboundPickingDo();
//                                        BeanUtils.copyProperties(item, pickingDo);
                                        OutboundPickingDo pickingDo = BeanConvert.INSTANCE.copyPickingDo(item);
                                        pickingDo.setId(null);
                                        pickingDo.setOrderType(OutboundOrderKind.allocate.getValue());
                                        pickingDo.setQty(needUpdate.get());
                                        pickingDo.setPickFlag(Boolean.TRUE);
                                        pickingService.save(pickingDo);
                                        noPickingDoList.add(pickingDo);
                                        needUpdate.set(0);
                                    }
                                });
                    } else {
                        //实际库位拣货数量等于推荐拣货数量的部分
                        pickingService.lambdaUpdate()
                                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                                .eq(OutboundPickingDo::getOutboundProductId, pickingDetail.getAllocatePlanItemId())
                                .eq(OutboundPickingDo::getRackId, rackId)
                                .set(OutboundPickingDo::getPickFlag, Boolean.TRUE)
                                .update();
                        noPickingDoList.forEach(noPickingDo -> {
                            if (noPickingDo.getOutboundProductId().equals(pickingDetail.getAllocatePlanItemId())) {
                                if (noPickingDo.getRackId().equals(rackId)) {
                                    noPickingDo.setPickFlag(Boolean.TRUE);
                                }
                            }
                        });
                        if (pickingQty > noPickingQty) {
                            //实际库位拣货数量大于推荐拣货数量的部分
                            //锁定库存
                            AtomicInteger needLock = new AtomicInteger(pickingQty - noPickingQty);
                            List<ProductStockRackDo> productStockRackDoList = productStockRackService.getFirstInFirstOutBySku(
                                    tenantId,
                                    planDo.getDeliveryWhId(),
                                    pickingDetail.getProductId(),
                                    needLock.get(),
                                    pickingCell.getCellId()
                            );
                            this.lockStockAndSavePicking(
                                    productStockRackDoList,
                                    needLock,
                                    pickingDetail.getAllocatePlanItemId(),
                                    Boolean.TRUE
                            );
                            //解锁库存,优先解锁非选择的库位，再根据推荐库位解锁倒序解锁
                            AtomicInteger needUnlock = new AtomicInteger(pickingQty - noPickingQty);
                            List<OutboundPickingDo> unLckPickingList1 = noPickingDoList.stream()
                                    .filter(noPickingDo -> !pickingCellMap.containsKey(noPickingDo.getRackId())
                                                           && Boolean.FALSE.equals(noPickingDo.getPickFlag()))
                                    .collect(Collectors.toList());
                            outboundService.unLock(unLckPickingList1, noPickingDoList, needUnlock);
                            List<OutboundPickingDo> unLockPickingList2 = noPickingDoList.stream()
                                    .filter(noPickingDo -> pickingCellMap.containsKey(noPickingDo.getRackId())
                                                           && Boolean.FALSE.equals(noPickingDo.getPickFlag()))
                                    .collect(Collectors.toList());
                            outboundService.unLock(unLockPickingList2, noPickingDoList, needUnlock);
                        }
                    }
                }
                //实际库位不在推荐库位范围内
                for (Map.Entry<Long, AllocatePickingBo.AllocatePickingCell> pickingCellEntry : pickingCellMap.entrySet()) {
                    Long cellId = pickingCellEntry.getKey();
                    Integer qty = pickingCellEntry.getValue().getQty();
                    if (noPickingDoListMap.containsKey(cellId)) {
                        continue;
                    }
                    //锁定库存
                    AtomicInteger needLock = new AtomicInteger(qty);
                    List<ProductStockRackDo> productStockRackDoList = productStockRackService.getFirstInFirstOutBySku(
                            tenantId,
                            planDo.getDeliveryWhId(),
                            pickingDetail.getProductId(),
                            needLock.get(),
                            cellId
                    );
                    this.lockStockAndSavePicking(
                            productStockRackDoList,
                            needLock,
                            pickingDetail.getAllocatePlanItemId(),
                            Boolean.TRUE
                    );
                    //解锁库存,优先解锁非选择的库位，再根据推荐库位解锁倒序解锁
                    AtomicInteger needUnlock = new AtomicInteger(qty);
                    List<OutboundPickingDo> unLckPickingList1 = noPickingDoList.stream()
                            .filter(noPickingDo -> !pickingCellMap.containsKey(noPickingDo.getRackId())
                                                   && Boolean.FALSE.equals(noPickingDo.getPickFlag()))
                            .collect(Collectors.toList());
                    outboundService.unLock(unLckPickingList1, noPickingDoList, needUnlock);
                    List<OutboundPickingDo> unLockPickingList2 = noPickingDoList.stream()
                            .filter(noPickingDo -> pickingCellMap.containsKey(noPickingDo.getRackId())
                                                   && Boolean.FALSE.equals(noPickingDo.getPickFlag()))
                            .collect(Collectors.toList());
                    outboundService.unLock(unLockPickingList2, noPickingDoList, needUnlock);
                }
            } else {
                //超拣
                throw new BizException(SysConstant.PickingQty_Exceeds_ShouldPickingQty, pickingDetail.getProductId());
            }
            //修改调拨产品实际拣货数量
            allocatePlanItemService.lambdaUpdate()
                    .eq(AllocatePlanItemDo::getOrderId, bo.getId())
                    .eq(AllocatePlanItemDo::getProductId, pickingDetail.getProductId())
                    .setSql("actual_shipments = actual_shipments + " + pickingTotalQty)
                    .update();
        }
        List<AllocatePickingBo.AllocatePickingCell> pickingCellList = pickingDetails.stream()
                .map(AllocatePickingBo.AllocatePickingDetail::getPickingCellList)
                .flatMap(List::stream)
                .collect(Collectors.toList());
        //计算本次拣货件数
        Integer thisTimePickingQty = pickingCellList.stream()
                .map(AllocatePickingBo.AllocatePickingCell::getQty)
                .reduce(0, Integer::sum);
        //计算应拣数
        Integer shouldPickingQty = planItemDoList.stream()
                .map(item -> item.getActualShipments() < item.getLockQty() ? item.getLockQty() - item.getActualShipments() : 0)
                .reduce(0, Integer::sum);
        boolean pickingFinish = thisTimePickingQty.equals(shouldPickingQty);
        if (pickingFinish) {
            //拣货完成
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Allocate_Pick_Finish,
                    "",
                    bo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
            //拣货时间&状态
            this.lambdaUpdate()
                    .eq(AllocatePlanDo::getId, bo.getId())
                    .set(AllocatePlanDo::getPickingFinishTime, DateUtil.date())
                    .set(AllocatePlanDo::getStatus, AllocatePlanStatus.three.getValue())
                    .update();
            return true;
        }
        return false;
    }

    @Override
    public SingleResult allocateOutboundOperation(Long id, Long userId, String username) {
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        vo.setTotal(1);
        AllocatePlanDo planDo = this.getById(id);
        if (Objects.isNull(planDo)) {
            vo.setFailed(vo.getFailed() + 1);
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", id);
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Not_Found_Outbound_Fail));
            vo.getFailedList().add(map);
            return SingleResult.success(vo);
        }
        if (Objects.nonNull(planDo.getMarkExceptionTime())) {
            vo.setFailed(vo.getFailed() + 1);
            HashMap<String, Object> map = new HashMap<>(4);
            map.put("code", planDo.getPlanNo());
            map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Marked_Exception_Outbound_Fail));
            vo.getFailedList().add(map);
            return SingleResult.success(vo);
        }
        this.allocateOutboundOperateAfterHandler(planDo, userId, username);
        vo.setSuccess(vo.getSuccess() + 1);
        return SingleResult.success(vo);
    }

    @Override
    public SingleResult allocateOutboundOperationBatch(List<Long> ids, Long userId, String username) {
        CommonBatchOperationVo vo = new CommonBatchOperationVo();
        vo.setTotal(ids.size());
        Map<Long, AllocatePlanDo> idMatchDo = this.listByIds(ids).stream()
                .collect(Collectors.toMap(AllocatePlanDo::getId, item -> item));
        for (Long id : ids) {
            AllocatePlanDo planDo = idMatchDo.get(id);
            if (Objects.isNull(planDo)) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", id);
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Not_Found_Outbound_Fail));
                vo.getFailedList().add(map);
                continue;
            }
            if (Objects.nonNull(planDo.getMarkExceptionTime())) {
                vo.setFailed(vo.getFailed() + 1);
                HashMap<String, Object> map = new HashMap<>(4);
                map.put("code", planDo.getPlanNo());
                map.put("errorMsg", StringsUtil.createI18Message(SysConstant.Marked_Exception_Outbound_Fail));
                vo.getFailedList().add(map);
                continue;
            }
            this.allocateOutboundOperateAfterHandler(planDo, userId, username);
            vo.setSuccess(vo.getSuccess() + 1);
        }
        return SingleResult.success(vo);
    }

    /**
     * 调拨发货计划出库后置操作
     */
    @Transactional(rollbackFor = Exception.class)
    public void allocateOutboundOperateAfterHandler(AllocatePlanDo planDo, Long userId, String username) {
        operateRecordService.operateAddRecordAndValue(
                SysConstant.OrderOutbound_Confirm_Outbound,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        //修改状态、时间、实际发货件数、实际发货箱数
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, planDo.getId())
                .set(AllocatePlanDo::getOutboundTime, DateUtil.date())
                .set(AllocatePlanDo::getStatus, AllocatePlanStatus.four.getValue())
                .set(AllocatePlanDo::getTotalActualShipments, planItemDoList.stream().map(AllocatePlanItemDo::getActualShipments).reduce(0, Integer::sum))
                .set(AllocatePlanDo::getTotalActualBox, planDo.getTotalPlanBox())
                .update();
        Map<Long, AllocatePlanItemDo> planItemIdMatchDo = planItemDoList.stream().collect(Collectors.toMap(AllocatePlanItemDo::getId, item -> item));
        Map<Long, ProductDo> productIdMatch = productService.lambdaQuery()
                .in(ProductDo::getId, planItemDoList.stream().map(AllocatePlanItemDo::getProductId).collect(Collectors.toList()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        //锁定的库存扣除
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .in(OutboundPickingDo::getOutboundProductId, planItemDoList.stream().map(AllocatePlanItemDo::getId).collect(Collectors.toList()))
                .eq(OutboundPickingDo::getPickFlag, Boolean.TRUE)
                .list();
        //查询库位信息 用于匹配库存属性
        StorageLocationQueryBo slBo = new StorageLocationQueryBo();
        slBo.setIdList(pickingDoList.stream().map(OutboundPickingDo::getRackId).collect(Collectors.toSet()));
        slBo.setTenantId(planDo.getTenantId());
        slBo.setPageSize(1000);
        Map<Long, StorageLocationVo> cellIdMatchDo = storageLocationService.getListPage(slBo).getData()
                .stream().collect(Collectors.toMap(StorageLocationVo::getId, val -> val));
        String batchNo = DateUtils.dateFormatToString(planDo.getPushWmsTime(), "yyyyMMdd");
        for (OutboundPickingDo pickingDo : pickingDoList.stream().sorted(Comparator.comparing(OutboundPickingDo::getSubStockId)).collect(Collectors.toList())) {
            productStockRackService.lambdaUpdate()
                    .eq(ProductStockRackDo::getId, pickingDo.getSubStockId())
                    .setSql("lock_quantity = lock_quantity - " + pickingDo.getQty())
                    .update();
            AllocatePlanItemDo planItemDo = planItemIdMatchDo.get(pickingDo.getOutboundProductId());
            if (Objects.nonNull(planItemDo)) {
                //库存流水
                stockFlowingService.insertProForOut(
                        ProductFlowingOutEt.builder()
                                .sku(productIdMatch.get(planItemDo.getProductId()).getSku())
                                .companyId(planDo.getCompanyId())
                                .warehouseId(planDo.getDeliveryWhId())
                                .rackId(pickingDo.getRackId())
                                .stockAttribute(cellIdMatchDo.get(pickingDo.getRackId()).getMainType())
                                .bookQuantity(-pickingDo.getQty())
                                .orderType(StockOrderTypeEnum.Allocate.getValue())
                                .noteNo(planDo.getPlanNo())
                                .innerBatchNo(batchNo)
                                .tenantId(planDo.getTenantId())
                                .type(StockTypeEnum.one.getValue())
                                .build()
                );
            }
            pickingService.removeById(pickingDo.getId());
        }
        //忽略进行中的截单，直接出库
        boolean isCancel = cancelService.lambdaUpdate()
                .eq(OutboundCancelDo::getOrderKind, OutboundOrderKind.allocate.getValue())
                .eq(OutboundCancelDo::getOutboundId, planDo.getId())
                .eq(OutboundCancelDo::getStatus, 0)
                .set(OutboundCancelDo::getStatus, 2)
                .set(OutboundCancelDo::getFinishTime, DateUtil.date())
                .update();
        if (isCancel) {
            operateRecordService.operateAddRecordAndValue(
                    SysConstant.Order_Interception_Fail,
                    "",
                    planDo.getId(),
                    userId,
                    username,
                    OperateTypeEnum.AllocatePlan
            );
            this.lambdaUpdate()
                    .eq(AllocatePlanDo::getId, planDo.getId())
                    .set(AllocatePlanDo::getInterceptionFlag, Boolean.FALSE)
                    .update();
        }
        context.publishEvent(new AllocateAddCostEvent(planDo, 0));
    }

    @Override
    public OrderOutCostReq buildOutboundCostReq(AllocatePlanDo planDo, Integer status) {
        OrderOutCostReq req = new OrderOutCostReq();
        req.setStatus(status);
        req.setOrderNo(planDo.getPlanNo());
        req.setOrderId(planDo.getId());
        req.setWhId(planDo.getDeliveryWhId());
        req.setCompanyId(planDo.getCompanyId());
        req.setTenantId(planDo.getTenantId());
        req.setOmsOrderCreateTime(planDo.getCreateTime());
        req.setOrderType(StaticDict.Cost_Out_OrderType.DiaoBo.getValue());
        req.setTrackId(planDo.getLogisticsChannelId());
        req.setInsureFlag(false);
        req.setInsureMoney(0d);
        req.setSignatureType(null);
        req.setOrderLimit(0);
        //地址簿
        AddressBookDo addressBookDo = addressBookService.getById(planDo.getAddressBookId());
        req.setProvinceCode(addressBookDo.getProvinceName());
        req.setCityName(addressBookDo.getCityName());
        req.setWarehouseId(planDo.getDeliveryWhId());
        req.setCountryCode(addressBookDo.getCountryRegionCode());
        req.setPostcode(addressBookDo.getPostCode());
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        Map<Long, ProductDo> productIdMatchDo = productService.lambdaQuery()
                .in(ProductDo::getId, planItemDoList.stream().map(AllocatePlanItemDo::getProductId).collect(Collectors.toSet()))
                .list().stream().collect(Collectors.toMap(ProductDo::getId, val -> val));
        req.setGoodsType(2);
        List<PackageSizeVo> packageSizeVoList = new ArrayList<>();
        List<TrialProductReq> products = new ArrayList<>();
        for (AllocatePlanItemDo planItemDo : planItemDoList) {
            ProductDo productDo = productIdMatchDo.get(planItemDo.getProductId());
            if (productDo == null) {
                continue;
            }
//            TrialProductReq trialProductReq = new TrialProductReq();
//            BeanUtils.copyProperties(productDo, trialProductReq);
            TrialProductReq trialProductReq = BeanConvert.INSTANCE.productDoToTrialProductReq(productDo);
            trialProductReq.setProductId(productDo.getId());
            trialProductReq.setSku(productDo.getSku());
            trialProductReq.setQuantity(status == 1 ? planItemDo.getPlanShipments() : planItemDo.getActualShipments());
            trialProductReq.setCategoryId(productDo.getCategoryId());
            products.add(trialProductReq);
            packageSizeVoList.add(
                    PackageSizeVo.builder()
                            .productId(productDo.getId())
                            .sku(productDo.getSku())
                            .qty(planItemDo.getActualShipments())
                            .length(productDo.getWmsLength() == null ? productDo.getLength() : productDo.getWmsLength())
                            .width(productDo.getWmsWidth() == null ? productDo.getWidth() : productDo.getWmsWidth())
                            .height(productDo.getWmsHeight() == null ? productDo.getHeight() : productDo.getWmsHeight())
                            .weight(productDo.getWmsWeight() == null ? productDo.getWeight() : productDo.getWmsWeight())
                            .build()
            );
        }
        req.setProducts(products);
        TrialBoxReq boxReq = this.calculatePackageSize(packageSizeVoList);
        boxReq.setQuantity(1);
        if (StrUtil.isNotBlank(planDo.getTrackingNumber())) {
            boxReq.setTrackNo(planDo.getTrackingNumber());
        }
        req.setBoxes(Collections.singletonList(boxReq));
        return req;
    }

    @Override
    public SingleResult markException(Long id, Long userId, String username) {
        AllocatePlanDo planDo = this.getById(id);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        if (Objects.nonNull(planDo.getMarkExceptionTime())) {
            throw new BizException(SysConstant.Order_Already_Mark_Exception);
        }
        if (planDo.getInterceptionFlag()) {
            throw new BizException(SysConstant.Allocate_Plan_Already_Interception);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.OrderOutbound_Mark_Exception,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, planDo.getId())
                .set(AllocatePlanDo::getMarkExceptionTime, DateUtil.date())
                .update();
        return SingleResult.success();
    }

    @Override
    public SingleResult removeException(Long id, Long userId, String username) {
        AllocatePlanDo planDo = this.getById(id);
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.OrderOutbound_Remove_Exception,
                "",
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, planDo.getId())
                .set(AllocatePlanDo::getMarkExceptionTime, null)
                .update();
        return SingleResult.success();
    }

    @Override
    public PageResult exceptionOrderPageList(ExceptionAllocateQueryBo bo) {
        Integer current = bo.getCurrent();
        Integer pageSize = bo.getPageSize();
        PageHelper.startPage(current, pageSize);
        List<ExceptionAllocateVo> list = baseMapper.getExceptionAllocateListPage(bo);
        PageInfo<ExceptionAllocateVo> page = new PageInfo<>(list);
        return new PageResult<>(list, current, pageSize, (int) page.getTotal(), page.getPages());
    }

    @Override
    public SingleResult exceptionConfirm(ExceptionConfirmBo bo, Long userId, String username) {
        AllocatePlanDo planDo = this.getById(bo.getId());
        if (Objects.isNull(planDo)) {
            throw new BizException(SysConstant.AllocateBatchPlan_Not_Exist);
        }
        operateRecordService.operateAddRecordAndValue(
                SysConstant.OrderOutbound_Confirm_Exception,
                bo.getExceptionReason(),
                planDo.getId(),
                userId,
                username,
                OperateTypeEnum.AllocatePlan
        );
        this.lambdaUpdate()
                .eq(AllocatePlanDo::getId, planDo.getId())
                .set(AllocatePlanDo::getStatus, AllocatePlanStatus.five.getValue())
                .set(AllocatePlanDo::getExceptionReason, bo.getExceptionReason())
                .set(AllocatePlanDo::getExceptionTime, DateUtil.date())
                .update();
        //异常订单如存在锁定库存需要释放
        List<AllocatePlanItemDo> planItemDoList = allocatePlanItemService.lambdaQuery()
                .eq(AllocatePlanItemDo::getOrderId, planDo.getId())
                .eq(AllocatePlanItemDo::getDeleteFlag, SysConstant.DELETE_FLAG_FALSE)
                .list();
        List<OutboundPickingDo> pickingDoList = pickingService.lambdaQuery()
                .eq(OutboundPickingDo::getOrderType, OutboundOrderKind.allocate.getValue())
                .in(OutboundPickingDo::getOutboundProductId, planItemDoList.stream().map(AllocatePlanItemDo::getId).collect(Collectors.toList()))
                .list();
        if (CollUtil.isNotEmpty(pickingDoList)) {
            outboundService.unLock(
                    JSONUtil.toList(JSONUtil.toJsonStr(pickingDoList), OutboundPickingDo.class),
                    pickingDoList,
                    new AtomicInteger(pickingDoList.stream().map(OutboundPickingDo::getQty).reduce(0, Integer::sum))
            );
        }
        return SingleResult.success();
    }

    public TrialBoxReq calculatePackageSize(List<PackageSizeVo> packageSizeVoList) {
        TrialBoxReq boxReq = new TrialBoxReq();
        packageSizeVoList.stream().map(PackageSizeVo::getLength).max(BigDecimal::compareTo).ifPresent(item -> {
            boxReq.setLength(item);
            boxReq.setLengthBs(item.divide(BigDecimal.valueOf(2.54), 3, RoundingMode.HALF_UP));
        });
        packageSizeVoList.stream().map(PackageSizeVo::getWidth).max(BigDecimal::compareTo).ifPresent(item -> {
            boxReq.setWidth(item);
            boxReq.setWidthBs(item.divide(BigDecimal.valueOf(2.54), 3, RoundingMode.HALF_UP));
        });
        //所有相加
        BigDecimal height = packageSizeVoList.stream().map(i -> i.getHeight().multiply(BigDecimal.valueOf(i.getQty()))).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal weight = packageSizeVoList.stream().map(i -> i.getWeight().multiply(BigDecimal.valueOf(i.getQty()))).reduce(BigDecimal.ZERO, BigDecimal::add);
        boxReq.setHeight(height);
        boxReq.setHeightBs(height.divide(BigDecimal.valueOf(2.54), 3, RoundingMode.HALF_UP));
        boxReq.setWeight(weight);
        boxReq.setWeightBs(weight.multiply(BigDecimal.valueOf(2.2046)).setScale(3, RoundingMode.HALF_UP));
        return boxReq;
    }
}